iT邦幫忙

2022 iThome 鐵人賽

DAY 20
0
Modern Web

開始搞懂React生態系系列 第 20

Day 20 全站狀態管理的利器 - Redux (二) Reducer

  • 分享至 

  • xImage
  •  

前情提要

Redux API 重點 - 分別是 Store、Actions、Reducer

前篇介紹了 Store 及 Actions,這篇就來詳細介紹 Reducer

Reducer 介紹

Reducer 是用來應對改變 State 的函式,它是一個 Pure Function,接受兩個參數,先前的 State 和 Action。

當 Store 收到 Action 通知後,一定要傳回一個「全新的State」,這樣 React Component 才能發生變化。

(previousState, action) => newState

Store 接收到 Action 傳來的資料,然後根據邏輯計算資料,這個過程就稱為 Reducer。這個 Function 之所以被稱為 Reducer,是因為它就像是 Array.prototype.reduce(reducer, ?initialValue) 的 Pure Function 類型。

Pure Function - 將相同的輸入丟入,永遠都會回傳相同的輸出,並且不對任何該函數以外的任何作用域產生影響。

Reducer 設計重點

接下來我們可以假定 TODO MVC 為想要設計的應用程式,來思考 Reducer 要怎麼做設計

設計適當的 State 結構

觀察 Todo MVC 的畫面,我們可以發現整個應用程式需要二個 State 如下,分別是 filter 字串,及 todos 陣列清單。

{
  filter: 'ALL', // ACTIVE, COMPLETED 
  todos: [
    {
      text: '測試A',
      completed: true,
    },
    {
      text: '測試B',
      completed: false
    }
  ]
}

設計 Reducer Function 內的 Action 與 State 對應

Reducer 是一個 Pure function,它接收先前的 State 和一個 action,然後回傳一個「全新的State」。

(previousState, action) => newState

首先,先定義一個 Initial State,如果傳入的 State 未定義,就設定Initial State給它,如果不是定義好的 Actions,都回傳先前的 State。

const initialState = {
  filter: 'ALL',
  todos: []
};

const todosReducer = (state = initialState, action) => {
  switch (action.type) {
    ...
    default:
      return state
  }
}

處理更多 Action

觀察 Todo MVC 的畫面操作,我們可以發現至少需要制定以下幾個 Action,且思考要回傳的全新的 State 的結構。

  • 新增一個 TODO 項目 - ADD_TODO
  • 把一個 TODO 項目標示是否完成 - TOGGLE_TODO
  • 依照 Filter 去顯示 TODO 項目 - SET_FILTER
const todosReducer = (state = initialState, action) => 
  switch (action.type) {
    case SET_FILTER: // 依照 Filter 去顯示 TODO 項目
      return Object.assign({}, state, {
        filter: action.filter
      });
    case ADD_TODO: // 新增一個 TODO 項目
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      });
    case TOGGLE_TODO: // 把一個 TODO 項目標示是否完成
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) => {
          if (index === action.index) {
            return Object.assign({}, todo, {
              completed: !todo.completed
            })
          }
          return todo
        })
      });      
    default: // 未知的 Action
      return state;
  }
}

使用 Object.assign 的方式,可確保永遠都是全新的 State,如果覺得這樣的操作很麻煩,也可以考慮引用第三方的函式庫,像是 Immutable.js 或是 Immer.js 來幫助開發。

拆分 Reducers

仔細觀察 State,可以發現 todos 和 filter 的更新似乎是完全獨立的。操作 todos 及 filter 的 Action 對應,也可以拆分成二塊,所以我們可以拆分前面的 Reducer Function,變成二個 Reducer Function,且各自更新它們對應的 State。

const todosReducer = (state = [], action)  => {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ];
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          });
        }
        return todo;
      });
    default:
      return state;
  }
};

const filterReducer = (state = 'ALL', action) => {
  switch (action.type) {
    case SET_FILTER:
      return action.filter;
    default:
      return state;
  }
};

請記住這些 Reducer 每一個都管理它所擁有的全域 State 一部分。每個 Reducer 的 State 參數都不一樣,並對應到它管理的部分 State。
隨著應用程式大小增長,我們可以把 Reducers 拆分成個別的檔案並讓它們完全獨立並管理不同的資料領域。

Slice

拆分成各別區域狀態的 State,可稱之為 Slice (切片),所以對應的 Reducer 就稱為 Slice Reducer。未來介紹 Redux Toolkit 時會再出現一次此名詞。

組合 Reducers 以及與 Store 連結

Redux 提供一個 utility 叫做 combineReducers(),可以用來組合多個 Slice Reducer。

把組合好的 reducers 傳入到 createStore 函式中,就完成 Reducers 與 Store 的連結。

import {createStore, combineReducers} from 'redux';

const reducers = combineReducers(todosReducer, filterReducer);

cosnt store = createStore(reducers);

export default store;

Next

在前面二篇,我們把 Redux API 的重點 Store、Actions、Reducer 都介紹完成了,也用範例來示範了建置的方式,接下來就要示範 React Compoent 要如何去使用 Redux 來完成狀態管理。

Reference

https://www.freecodecamp.org/news/what-is-redux-store-actions-reducers-explained/

https://www.laitimes.com/article/1od0a_1u849.html

https://chentsulin.github.io/redux/index.html

https://pjchender.dev/webdev/note-without-redux/

https://ithelp.ithome.com.tw/articles/10219962


上一篇
Day 19 全站狀態管理的利器 - Redux (一) Store and Action
下一篇
Day 21 全站狀態管理的利器 - Redux (三) 元件使用 Redux
系列文
開始搞懂React生態系30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言